cmake_minimum_required (VERSION 3.5)

project (mifare C)

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../script/bin)
set(SRC_DIR ./) # Assuming source files are in the same directory as CMakeLists.txt

# Define a variable for the compatibility code directory
set(COMPAT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/compat)

set(COMMON_FILES
    ${SRC_DIR}/common.c
    ${SRC_DIR}/crapto1.c
    ${SRC_DIR}/crypto1.c
    ${SRC_DIR}/bucketsort.c
    ${SRC_DIR}/parity.c)

set(
    NESTED_UTIL
    ${SRC_DIR}/nested_util.c
)

set(
    MFKEY_UTIL
    ${SRC_DIR}/mfkey.c
)

# --- liblzma Build ---
# NOTE: Ensure the path 'xz' matches the actual directory name containing liblzma source
set(LIBLZMA_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/xz)
# Define the build directory *relative* to the liblzma source directory
set(LIBLZMA_BUILD_SUBDIR build)
set(LIBLZMA_BUILD_DIR ${LIBLZMA_SRC_DIR}/${LIBLZMA_BUILD_SUBDIR})

# Define CMake arguments for configuring liblzma
set(LIBLZMA_CMAKE_ARGS
    -DXZ_TOOL_XZ=OFF
    -DXZ_TOOL_XZDEC=OFF
    -DXZ_TOOL_LZMADEC=OFF
    -DXZ_TOOL_LZMAINFO=OFF
    -DXZ_TOOL_SCRIPTS=OFF
    -DXZ_DOC=OFF
    -DXZ_NLS=OFF
    -DXZ_DOXYGEN=OFF
    -DBUILD_SHARED_LIBS=OFF # Ensure static lib is built
    -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
)
# Add platform-specific args
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
    list(APPEND LIBLZMA_CMAKE_ARGS "-DXZ_SANDBOX=no")
endif()

# --- Define the expected path for the built liblzma library ---
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
    if(MSVC)
        # Point to the Release directory as the build command uses --config Release
        set(LIBLZMA_LIB_PATH "${LIBLZMA_BUILD_DIR}/Release/lzma.lib")
    else() # MinGW / Ninja
        # Assuming liblzma.a goes directly into build/ for non-MSVC Windows
        set(LIBLZMA_LIB_PATH "${LIBLZMA_BUILD_DIR}/liblzma.a")
    endif()
else()
    # Single-config (Linux Makefiles/Ninja): Library is typically directly in the build directory
    set(LIBLZMA_LIB_PATH "${LIBLZMA_BUILD_DIR}/liblzma.a")
endif()
message(STATUS "Expecting liblzma at: ${LIBLZMA_LIB_PATH}")

# --- Use add_custom_command to declare the output file and the commands to create it ---
add_custom_command(
    OUTPUT ${LIBLZMA_LIB_PATH} # Declare the file that will be generated
    # Command 1: Configure liblzma
    COMMAND ${CMAKE_COMMAND} -B ${LIBLZMA_BUILD_SUBDIR} -S . ${LIBLZMA_CMAKE_ARGS} -G "${CMAKE_GENERATOR}" # Pass generator
    # Command 2: Build liblzma (using CMake --build)
    COMMAND ${CMAKE_COMMAND} --build ${LIBLZMA_BUILD_SUBDIR} --config Release # Force Release build for liblzma
    WORKING_DIRECTORY ${LIBLZMA_SRC_DIR}
    DEPENDS ${LIBLZMA_SRC_DIR}/CMakeLists.txt # Re-run if xz's CMakeLists changes
    COMMENT "Configuring and building liblzma (${LIBLZMA_LIB_PATH})"
    VERBATIM
    USES_TERMINAL # Show output during build
)

# --- Custom target that DEPENDS on the output file ---
# This target ensures the add_custom_command runs.
# Add ALL so it runs as part of the default build.
add_custom_target(build_liblzma ALL
    DEPENDS ${LIBLZMA_LIB_PATH} # Depend on the output file generated by add_custom_command
)

# --- Create an IMPORTED library target for liblzma ---
add_library(liblzma_imported STATIC IMPORTED GLOBAL)
set_target_properties(liblzma_imported PROPERTIES
    IMPORTED_LOCATION "${LIBLZMA_LIB_PATH}"
    INTERFACE_INCLUDE_DIRECTORIES "${LIBLZMA_SRC_DIR}/src/liblzma/api" # Public include path
)

# --- Ensure the IMPORTED target depends on the custom target ---
add_dependencies(liblzma_imported build_liblzma)


# --- Hardnested Recovery Sources ---
set(HARDNESTED_RECOVERY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/HardnestedRecovery)

set(HARDNESTED_SOURCES
    ${HARDNESTED_RECOVERY_DIR}/hardnested_main.c
    ${HARDNESTED_RECOVERY_DIR}/pm3/ui.c
    ${HARDNESTED_RECOVERY_DIR}/pm3/util.c
    ${HARDNESTED_RECOVERY_DIR}/cmdhfmfhard.c
    ${HARDNESTED_RECOVERY_DIR}/pm3/commonutil.c
    ${HARDNESTED_RECOVERY_DIR}/hardnested/hardnested_bf_core.c
    ${HARDNESTED_RECOVERY_DIR}/hardnested/hardnested_bruteforce.c
    ${HARDNESTED_RECOVERY_DIR}/hardnested/hardnested_bitarray_core.c
    ${HARDNESTED_RECOVERY_DIR}/hardnested/tables.c
)
if(NOT CMAKE_SYSTEM_NAME MATCHES "Windows")
    list(APPEND HARDNESTED_SOURCES ${HARDNESTED_RECOVERY_DIR}/pm3/util_posix.c)
endif()


# --- Platform specific settings ---
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
    MESSAGE(STATUS "Run on linux.")
    if (CMAKE_BUILD_TYPE STREQUAL "Release")
        set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
    endif()
    find_package(Threads REQUIRED)
    set(LIBTHREAD Threads::Threads) # Use modern target
    set(LIBMATH m)

elseif (CMAKE_SYSTEM_NAME MATCHES "Windows")
    MESSAGE(STATUS "Run on Windows.")
    if (CMAKE_BUILD_TYPE STREQUAL "Release")
        # Set optimization flags based on compiler
        if(MSVC)
            set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Ox")
        else() # Assuming MinGW or similar GCC-compatible
            set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
        endif()
    endif()

    # --- Pthread library handling for Windows ---
    if(MSVC)
        # MSVC: Find the specific pthreads-win32 library
        message(STATUS "MSVC compiler detected. Looking for pthreads-win32 library.")
        find_library(PTHREAD_LIB_PATH pthreadVC2.lib PATHS ${CMAKE_CURRENT_SOURCE_DIR}/lib/pthread/lib/x64/)
        if (NOT PTHREAD_LIB_PATH)
            message(FATAL_ERROR "pthreadVC2.lib not found in ${CMAKE_CURRENT_SOURCE_DIR}/lib/pthread/lib/x64/. Please provide pthreads-win32 for MSVC.")
        endif()

        # Create an imported library for pthread on Windows for consistency
        add_library(pthread STATIC IMPORTED GLOBAL)
        set_target_properties(pthread PROPERTIES
            IMPORTED_LOCATION ${PTHREAD_LIB_PATH}
            INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/lib/pthread/include
        )
        set(LIBTHREAD pthread) # Use the imported target name

    elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") # Check for MinGW (GCC) or Clang on Windows
        # MinGW or Clang on Windows: Use find_package(Threads) to find the bundled winpthreads
        message(STATUS "MinGW (GCC) or Clang compiler detected on Windows. Using find_package(Threads).")
        find_package(Threads REQUIRED)
        if(Threads_FOUND)
            set(LIBTHREAD Threads::Threads) # Use the modern CMake target
            message(STATUS "Found MinGW pthreads using find_package(Threads).")
        else()
            # This shouldn't happen if Threads is REQUIRED, but good practice
            message(FATAL_ERROR "Could not find pthreads using find_package(Threads) with MinGW/Clang. Check your toolchain installation.")
        endif()

    else()
        message(FATAL_ERROR "Unsupported Windows compiler: ${CMAKE_C_COMPILER_ID}. Cannot determine how to find pthreads.")
    endif()
    # --- End Pthread library handling ---

    set(LIBMATH "") # No separate math library needed on Windows

else()
    # Handle other platforms or provide a default/error
    MESSAGE(STATUS "Running on other platform: ${CMAKE_SYSTEM_NAME}")
    set(LIBMATH "")
    # Attempt to find Threads anyway, might fail gracefully or error depending on REQUIRED
    find_package(Threads)
    if(Threads_FOUND)
      set(LIBTHREAD Threads::Threads)
    else()
      message(WARNING "Threads library not found for platform ${CMAKE_SYSTEM_NAME}. Linking might fail.")
      set(LIBTHREAD "") # Set to empty or handle error
    endif()
endif()

# --- Executable Definitions ---

add_executable(nested ${COMMON_FILES} ${NESTED_UTIL} nested.c)
target_include_directories(nested PRIVATE ${SRC_DIR})
target_link_libraries(nested PRIVATE ${LIBTHREAD}) # Link common thread lib
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(nested PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    target_compile_definitions(nested PRIVATE HAVE_STRUCT_TIMESPEC)
    # No extra target_link_libraries needed here, ${LIBTHREAD} handles it
endif()


add_executable(staticnested ${COMMON_FILES} ${NESTED_UTIL} staticnested.c)
target_include_directories(staticnested PRIVATE ${SRC_DIR})
target_link_libraries(staticnested PRIVATE ${LIBTHREAD}) # Link common thread lib
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(staticnested PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    target_compile_definitions(staticnested PRIVATE HAVE_STRUCT_TIMESPEC)
    # No extra target_link_libraries needed here, ${LIBTHREAD} handles it
endif()


add_executable(darkside ${COMMON_FILES} ${MFKEY_UTIL} darkside.c)
target_include_directories(darkside PRIVATE ${SRC_DIR})
# darkside doesn't seem to need pthreads based on original file
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(darkside PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    target_compile_definitions(darkside PRIVATE HAVE_STRUCT_TIMESPEC)
endif()


add_executable(mfkey32 ${COMMON_FILES} mfkey32.c)
target_include_directories(mfkey32 PRIVATE ${SRC_DIR})
# mfkey32 doesn't seem to need pthreads based on original file
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(mfkey32 PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    target_compile_definitions(mfkey32 PRIVATE HAVE_STRUCT_TIMESPEC)
endif()


add_executable(mfkey32v2 ${COMMON_FILES} mfkey32v2.c)
target_include_directories(mfkey32v2 PRIVATE ${SRC_DIR})
# mfkey32v2 doesn't seem to need pthreads based on original file
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(mfkey32v2 PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    target_compile_definitions(mfkey32v2 PRIVATE HAVE_STRUCT_TIMESPEC)
endif()


add_executable(mfkey64 ${COMMON_FILES} mfkey64.c)
target_include_directories(mfkey64 PRIVATE ${SRC_DIR})
# mfkey64 doesn't seem to need pthreads based on original file
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(mfkey64 PRIVATE _GNU_SOURCE)
endif()
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    target_compile_definitions(mfkey64 PRIVATE HAVE_STRUCT_TIMESPEC)
endif()


# --- hardnested Executable ---
add_executable(hardnested ${COMMON_FILES} ${HARDNESTED_SOURCES})
add_dependencies(hardnested liblzma_imported) # Ensure liblzma is built first

target_include_directories(hardnested PRIVATE
    ${SRC_DIR}
    ${HARDNESTED_RECOVERY_DIR}
    ${HARDNESTED_RECOVERY_DIR}/pm3
    ${HARDNESTED_RECOVERY_DIR}/hardnested
    # liblzma include dir comes via INTERFACE property of liblzma_imported
)
target_compile_options(hardnested PRIVATE -Wall)

if (CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(hardnested PRIVATE _GNU_SOURCE)
endif()

# Platform-specific settings for Windows
if (CMAKE_SYSTEM_NAME MATCHES "Windows")

    # Settings common to all Windows builds (MSVC & MinGW)
    target_compile_definitions(hardnested PRIVATE
        HAVE_STRUCT_TIMESPEC
        LZMA_API_STATIC # Keep if needed for static linking of lzma
    )
    # No extra target_link_libraries needed here, ${LIBTHREAD} handles it below

    # Add fmemopen compatibility layer ONLY for non-MSVC Windows builds (e.g., MinGW)
    if(NOT MSVC)
        message(STATUS "Non-MSVC Windows build detected, adding fmemopen compatibility layer.")
        target_sources(hardnested PRIVATE
            ${COMPAT_DIR}/fmemopen/libfmemopen.c # Compile the source file
        )
        target_include_directories(hardnested PRIVATE
             ${COMPAT_DIR}/fmemopen # Add include directory for fmemopen.h
        )
    endif() # End NOT MSVC

endif() # End Windows

# Link libraries common to all platforms (or handled by variables)
target_link_libraries(hardnested PRIVATE
    ${LIBTHREAD}    # Handles pthread correctly now for Linux, MSVC, MinGW
    ${LIBMATH}      # Handles 'm' on Linux, empty on Windows
    liblzma_imported # Link against the IMPORTED target name
)

# Set the output directory for all executables at the end
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH})

